# 管理煮面流程
import sys
import time
import traceback
from queue import Queue

from PyQt5.QtCore import QObject, QTimer, pyqtSlot, pyqtSignal, QRunnable, QThreadPool, QDateTime, QMutex, QThread

import Cmd
import Pour
import action_sequence
from Arm import Arm
from Bowl import Bowl
from Config import Config
from ConveyorBelt import ConveyorBelt, ConveyorManager
from DiyArm import DiyArm, DiyArmInst
from GetBox import GetBoxAction
from Task import Task, TaskStatus
from Temperature import Temperature
from Track import Track
from Wrist import Wrist
from singleton import singleton
from Boiler import Boiler
from loguru import logger


class WorkerSignals(QObject):
    finished = pyqtSignal(object)
    error = pyqtSignal(tuple)
    result = pyqtSignal(object)
    progress = pyqtSignal(int)


class Worker(QRunnable):
    def __init__(self, fn, *args, **kwargs):
        super(Worker, self).__init__()

        # Store constructor arguments (re-used for processing)
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()

        # print(args)
        # print(kwargs)

        # Add the callback to our kwargs
        self.kwargs['progress_callback'] = self.signals.progress
        # exec_time

    @pyqtSlot()
    def run(self):
        '''
        Initialise the runner function with passed args, kwargs.
        '''

        # Retrieve args/kwargs here; and fire processing using them
        try:
            result = self.fn(*self.args, **self.kwargs)
        except:
            traceback.print_exc()
            exctype, value = sys.exc_info()[:2]
            self.signals.error.emit((exctype, value, traceback.format_exc()))
        else:
            self.signals.result.emit(result)  # Return the result of the processing
        finally:
            # logger.debug(self.args)
            self.signals.finished.emit(self.args)  # Done


hole_mutex = QMutex()


# class TaskFun(QRunnable):
#     def run(self):
#         while True:
#             hole_mutex.lock()
#             if not Boiler().is_hole_full():
#                 index = Boiler().get_one_empty_hole()
#                 Boiler().set_hole(index, True, None)
#                 hole_mutex.unlock()
#                 break
#             else:
#                 hole_mutex.unlock()
#                 continue
#
#             QThread.msleep(100)
#
#         while True:
#             if Boiler().is_boiling():
#                 break
#
#             QThread.msleep(100)


@singleton
class ProcessManager(QObject):
    bot_running = False
    get_box_queue = []
    pour_queue = []

    stat_signal = pyqtSignal(object)
    sig_temp = pyqtSignal(float)
    sig_track_pos = pyqtSignal(int)

    sig_queue_sum = pyqtSignal(int)
    sig_msg_report = pyqtSignal(str)

    sig_home_report = pyqtSignal(tuple)

    def __init__(self):
        super().__init__()

        self.task_list = []
        self.arm_result = Queue()

        self.threadpool = QThreadPool()
        self.threadpool.setMaxThreadCount(1)

        self.timer = QTimer()
        self.timer.setInterval(10)
        self.timer.timeout.connect(self.process)
        self.timer.start()

        self.devices_status_timer = QTimer()
        self.devices_status_timer.setInterval(1000)
        self.devices_status_timer.timeout.connect(self.get_devices_status)
        self.devices_status_timer.start()

        self.is_ready = False
        self.temp_msg = ""

        Boiler().init()
        ConveyorManager().init()
        logger.add('runtime_{time}.log', rotation='00:00', retention='10 days')

        DiyArmInst()
        DiyArmInst().event("DiyArm1").connect(self.action_finish)

    def task_event_dispatch(self):
        pass

    def is_get_actions_empty(self):
        return not self.get_box_queue

    def is_robot_ready(self):
        return self.is_ready

    def set_robot_ready(self, ready):
        self.is_ready = ready

    def report_queue_sum(self):
        self.sig_queue_sum.emit(len(self.get_box_queue))

    def report_pour_queue_sum(self):
        pass

    def report_message(self, msg):
        if self.temp_msg is not msg:
            self.temp_msg = msg
            self.sig_msg_report.emit(msg)

    def add_action(self):
        # self.get_box_queue.append(GetBoxAction(15))

        # p_action = Pour.Pour(0, 0)
        # self.pour_queue.append(p_action)
        # logger.debug(self.task_list)
        # t = Task()
        self.task_list.append(Task())
        # self.start_get_box(GetBoxAction(1, 888))

    def progress_fn(self, n):
        # print("%d%% done" % n)
        self.bot_running = True

    def pour_progress_fn(self, n):
        # print("pour %d%% done" % n)
        self.bot_running = True

    def exec_get_box_action(self, action, progress_callback):
        # todo 这里操作,把动作的单例放这里执行即可
        action_sequence.get_box_action(action)
        return "Done."

    def exec_pour_action(self, action, progress_callback):
        # todo exec some action here
        action_sequence.pour_action(action)
        return "Pour Done"

    def print_output(self, s):
        print(s)

    def thread_complete(self, args):
        task_id = args[0].task_id()
        # self.bot_running = False
        # 动作运行完后进入保温状态
        # Boiler().turn(False)

        # action = self.get_box_queue.pop(0)
        # p_action = Pour.Pour(20, 20)
        # p_action.set_hold_index(action.get_hold_index())
        # self.pour_queue.append(p_action)
        # task_id = 1
        self.arm_result.put(("dobot", task_id))

    def pour_thread_complete(self):
        print("POUR THREAD COMPLETE!")
        self.bot_running = False

        Boiler().set_heat_preservation(True)
        p_action = self.pour_queue.pop(0)
        Boiler().set_hold(p_action.get_hold_index(), False)

    def start_get_box(self, action):
        worker = Worker(self.exec_get_box_action, action)  # Any other args, kwargs are passed to the run function
        worker.signals.result.connect(self.print_output)
        worker.signals.finished.connect(self.thread_complete)
        worker.signals.progress.connect(self.progress_fn)

        self.bot_running = True
        Boiler().turn(True)
        self.threadpool.start(worker)

    def start_pour(self, action):
        # worker = Worker(self.exec_pour_action, action)  # Any other args, kwargs are passed to the run function
        # worker.signals.result.connect(self.print_output)
        # worker.signals.finished.connect(self.pour_thread_complete)
        # worker.signals.progress.connect(self.pour_progress_fn)
        #
        self.bot_running = True
        # Boiler().turn(False)
        #
        # self.threadpool.start(worker)

        cmds = Cmd.load_from_file("./cmds/action_pour")
        # logger.debug(cmds)
        DiyArmInst().add_cmd("DiyArm1", cmds, "pour")

    def home_action(self, progress_callback):
        # if not Track().home():
        #     return False, "Track home Timeout"
        progress_callback.emit(33)
        # Track().move_to(3000)
        # Track().wait_action(params={"wait_timeout": 20})
        if not Arm().home():
            return False, "Arm home Timeout"
        progress_callback.emit(67)
        # progress_callback.emit("Track home Success")
        # if not Wrist().home():
        #     return "Wrist home Timeout"
        # progress_callback.emit("Wrist home Success")

        return True, "Home operate success"

    def home_result(self, ret):
        self.set_robot_ready(True)
        self.sig_home_report.emit(ret)
        pass

    def home_finish(self):
        self.bot_running = False
        pass

    def home_progress(self, msg):
        self.sig_msg_report.emit("Home operate progress :{}".format(msg))
        pass

    def devices_home(self):
        if self.bot_running:
            return

        # if self.pour_queue or self.get_box_queue:
        #     return
        #
        cmds = Cmd.load_from_file("./cmds/action_home")
        # logger.debug(cmds)
        DiyArmInst().add_cmd("DiyArm1", cmds, "home")

        worker = Worker(self.home_action)
        worker.signals.result.connect(self.home_result)
        worker.signals.finished.connect(self.home_finish)
        worker.signals.progress.connect(self.home_progress)
        #
        self.bot_running = True
        self.threadpool.start(worker)

    def action_finish(self, result):
        # logger.debug(result)
        # logger.debug(action_name is "home")
        action_name = result[0]
        # if action_name == "home":
        #     self.home_result((True, "Home operate success"))
        # if action_name == "pour":
        #     self.pour_thread_complete()

        self.arm_result.put(result)

    def run(self):
        while Boiler.is_hold_full(): pass
        while not Boiler.is_boiling(): pass
        while not ConveyorManager.find_conveyor_with_bottle(): pass

    #     todo 这里好好想想如何写运行流程

    def process(self):
        # if self.bot_running:
        #     return
        # 有东西要做煮，打开锅炉
        # if self.get_box_queue:
        #     Boiler().turn(True)
        # else:
        #     Boiler().turn(False)

        if not self.arm_result.empty():
            result = self.arm_result.get_nowait()
            for t in self.task_list:
                if result[0] == "pour" and t.id == result[1]:  # 完成任务
                    t.set_pour_done()
                if result[0] == "dobot" and t.id == result[1]:
                    t.food_in_the_water()

        try:
            for t in self.task_list:
                # 找空位，占据空位
                # logger.debug(t)
                if t.status == TaskStatus.EMPTY:
                    # logger.debug(t.status)
                    if not Boiler().is_hole_full():
                        ret, index = Boiler().get_one_empty_hole()
                        if ret:
                            Boiler().set_hole(index, True, t.id)
                            try:
                                t.status = TaskStatus.BOILING
                            except Exception as e:
                                logger.debug("st {}", e)
                            t.hole_index = index
                # 等待水沸腾
                elif t.status == TaskStatus.BOILING:
                    try:
                        if Boiler().is_boiling():
                            t.status = TaskStatus.FOOD
                    except Exception as e:
                        logger.debug("FOOD {}", e)
                # 食物是否准备好
                elif t.status == TaskStatus.FOOD:
                    if not self.threadpool.activeThreadCount():
                        self.start_get_box(GetBoxAction(1, t.id))
                        t.status = TaskStatus.WAIT_FOOD_READY
                elif t.status == TaskStatus.WAIT_FOOD_READY:
                    # 食物放到水里的动作完成
                    if t.is_food_in_the_water():
                        t.status = TaskStatus.WAIT_FOR_TIMEOUT
                        t.boiled_time = time.time()
                elif t.status == TaskStatus.WAIT_FOR_TIMEOUT:
                    if t.is_boiled_timeout():
                        if not DiyArmInst().is_running("DiyArm1"):
                            t.status = TaskStatus.WAIT_FOR_POUR_DONE
                            # 放食物
                            cmds = Cmd.load_from_file("./cmds/action_pour")
                            # logger.debug(t.id)
                            DiyArmInst().add_cmd("DiyArm1", cmds, "pour", t.id)
                elif t.status == TaskStatus.WAIT_FOR_POUR_DONE:
                    if t.is_pour_done():
                        logger.debug("start pour")
                        t.status = TaskStatus.FINISH
                        Boiler().set_hole(t.hole_index, False, None)
                        logger.debug("任务{}完成 时间：{}".format(t.id, QDateTime.currentDateTime()))
                    # logger.debug("")
        except Exception as e:
            logger.debug(e)

        # Q2倒面的队列空
        # if not self.pour_queue:
        #     # Q1取物品的队列有数据
        #     if self.get_box_queue:
        #         # todo 考虑把炉子和碗的操作移动到action sequence里面？
        #         if not Boiler().is_boiling():
        #             Boiler().turn(True)
        #             # self.l.setText("等待水沸腾")
        #             self.stat_signal.emit("等待水沸腾,水温：{}".format(Temperature().get_temp()))
        #             return
        #
        #         Boiler().set_temp(100)
        #         if not Bowl().is_ready():
        #             # self.l.setText("等待碗放好")
        #             self.stat_signal.emit("等待碗放好")
        #             Bowl().place()
        #             return
        #
        #         if Boiler().is_hold_full():
        #             return
        #
        #         action = self.get_box_queue[0]
        #         action.set_hold_index(Boiler().get_one_empty_hold())
        #         self.start_get_box(action)
        #     else:#Q1,Q2都没有有数据
        #         # print("###3")
        #         pass
        # else:# Q2有数据
        #     # Q1空，运行Q2的程序
        #     if not self.get_box_queue:
        #         pour_action = self.pour_queue[0]
        #         # 时间间隔到了才执行倒的动作
        #         if pour_action.get_timeout() < time.time():
        #             self.start_pour(pour_action)
        #             return
        #
        #     else:#Q1,Q2都有数据，这时需要对比谁的优先级高
        #         get_action = self.get_box_queue[0]
        #         pour_action = self.pour_queue[0]
        #
        #         if pour_action.get_timeout() - time.time() > get_action.get_exec_time():
        #             self.start_get_box(get_action)
        #         else:
        #             self.start_pour(pour_action)

    def get_track_pos(self, progress_callback):
        progress_callback.emit(0)
        return Track().get_pos()

    def get_temp(self, progress_callback):
        progress_callback.emit(0)
        return Temperature().get_temp()

    # def get_boiler_status(self, pc):
    #     return (Boiler().current_status(), Boiler().)

    # 定时任务，在线程里面读取外设数据
    def get_devices_status(self):
        # worker = Worker(self.get_track_pos)
        # # worker.signals.result.connect(lambda pos: print("Track pos = {}".format(pos)))
        # worker.signals.result.connect(lambda pos: self.sig_track_pos.emit(pos))
        # self.threadpool.start(worker)

        worker = Worker(self.get_temp)
        # worker.signals.result.connect(lambda temp: print("Temp = {}".format(temp)))
        worker.signals.result.connect(lambda temp: self.sig_temp.emit(temp))
        self.threadpool.start(worker)

        self.report_queue_sum()

        # worker = Worker()
